﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace eBrain
{
    public partial class Exp_SmoothPursuit : Experiment
    {

        public enum SmoothPursuitMode { Steady, Line, Elipse, SinX, SinY, Spiral };

        //--------------------------------------------
        //   SmoothPursuit
        //--------------------------------------------
        public enum MoveLaw { Steady, Line, Sin };
        public MoveLaw x_MoveLaw, y_MoveLaw;
        int x = 0, y = 0, last_y;  //i valori delle coordinate correnti
        public List<PosSequenceElem> PosSequenceList;
        int MaxCounter = 0;
        int actCounter = 0;
        int LastCounter = 0;
        DateTime StartMotionTime;
        DateTime ActMoveStartTime = DateTime.MinValue;


        double last_t = 0.0; //


        public Exp_SmoothPursuit(TestControlWindow _tcw, ExperimentMainForm _expw)//, int _w, int _h)
            : base(_tcw, _expw)//, _w, _h) 
        { }

        public Exp_SmoothPursuit(ExperimentMainForm _expw, string datapath, string logpath, string sInputMode)
            : base(_expw, datapath, logpath, sInputMode)//, _w, _h) 
        { }

        override public void CustomInit()
        {
            PosSequenceList = new List<PosSequenceElem>();
        }


        public override void CustomXMLLoad(XmlDocument xmldoc)
        {
            //inseriso il tempo di aggiornamento in millisecondi
            UpdateTime = int.Parse(xmldoc.SelectSingleNode("Experiment/UpdateTime").InnerText.ToString());

            TargetSize = int.Parse(xmldoc.SelectSingleNode("Experiment/TargetSize").InnerText.ToString());
            TargetShape = xmldoc.SelectSingleNode("Experiment/TargetShape").InnerText.ToString();

            if (TargetShape == "Image")
            {
                TargetImage = xmldoc.SelectSingleNode("Experiment/TargetImage").InnerText.ToString();
            }
            else
            {
                myExpW.PrepareMaskAndTarget(TargetSize, bkgrnd, frgrnd);
                ////preparo la maschera ed il target
                //Mask = new Bitmap(TargetSize, TargetSize);
                //Graphics g = Graphics.FromImage(Mask);
                //g.Clear(bkgrnd);
                //g.Dispose();

                //Target = new Bitmap(TargetSize, TargetSize);
                //g = Graphics.FromImage(Target);
                //g.Clear(bkgrnd);

                //g.FillEllipse(new System.Drawing.SolidBrush(frgrnd), 0, 0, TargetSize, TargetSize);

                //g.Dispose();
            }
        }

        override public void StartTrials(bool TrainingMode)
        {
            bool go_on = false;
            try
            {
                myLog.Log("Start Trials: begin");

                bTrainingMode = TrainingMode;

                CountDown(3, 800);
                //StartPeriodicTimer();

                if (bUseSounds)
                    Beep(2000, 500);

                //StartPeriodicTimer();

                //----2011-1208
                //if (ExperimentType == _ExperimentType.Saccade)
                //{
                //    ASStatus = SaccadeStatus.WaitingToCenter;
                //    StartSaccadePeriodicTimer();
                //}
                //else if (ExperimentType != _ExperimentType.SmoothPursuit)
                //{
                //    StartPeriodicTimer();
                //}

                //UpdateCanvas();
                //Thread.Sleep(100);
                //PaintCanvasOnTCW();

                //if (ExpInputMode == InputMode.Eyetracker)
                //{

                //    if (!bEyelinkConnected)
                //        ET_ConnectEyelink();

                //    //ET_UpdateHostROI();
                //}

                //exp_status = ExperimentStatus.DoingTrials;
                EndOfTrials = false;

                actCounter = 0;
                ////Inizio a registrare soltanto se non sto effettuando il training iniziale
                //if (!bTrainingMode)
                if (ExpInputMode == InputMode.Eyetracker)
                    ET.StartRecording();

                //ATTENIONE: per lo smooth pursuit non va qui
                //StartPeriodicTimer();

                //this.Invalidate();
                myLog.Log("Start Trials: end");

                go_on = true;
            }
            catch (Exception ex)
            {
                myLog.Log("StartTrials error: " + ex.Message.ToString() + " - " + ex.InnerException.ToString());
                MessageBox.Show("StartTrials error: " + ex.Message.ToString() + " - " + ex.InnerException.ToString());
            }
            finally
            {
                //Se posso procedere, passo al primo Trial
                if (go_on && exp_status != ExperimentStatus.Ending)
                    DoNextTrial();
            }
        }


        /// <summary>
        /// 
        /// </summary>
        override public void DoNextTrial()
        {
            try
            {
                //myLog.Log("DoNextTrial: begin");

                //Nel caso stia lavorando con l'ET, allora invio le opportune segnalazioni all'ET
                if (ExpInputMode == InputMode.Eyetracker && bEyelinkConnected)
                {
                    //ET_LabelTrial();

                    if (!bConstantRois)
                        UpdateHostROI();
                }

                //Custom_DoNextTrial();
                CreatePosSequence();
                ClearCanvas();

                exp_status = ExperimentStatus.DoingTrials;

                GC.Collect();
                GC.WaitForPendingFinalizers();
                myExpW.Invalidate();

                //alzo la priorità del processo
                System.Diagnostics.Process myProcess = System.Diagnostics.Process.GetCurrentProcess();
                myProcess.PriorityClass = System.Diagnostics.ProcessPriorityClass.High;

                StartPeriodicTimer();

                //Thread.Sleep(20);

                if (ExperimentType != _ExperimentType.SmoothPursuit)
                    TimeoutTimer.Start();

                TrialStartTimeList.Add(DateTime.Now);
                //TrialStartTime = DateTime.Now;

                //myLog.Log("DoNextTrial: end");

            }
            catch (Exception ex)
            {
                myLog.Log("DoNextTrial error: " + ex.Message.ToString() + " - " + ex.Source.ToString());
                MessageBox.Show("DoNextTrial error: " + ex.Message.ToString() + " - " + ex.Source.ToString());
            }
        }


        override public void UpdateCoordTimerElapsedEvent(object sender, EventArgs e)
        {
            int xmin, xmax, ymin, ymax;
            double delta_x, delta_y, delta_t;
            bool bOldVersion = true;


            if (bOldVersion)
            {
                //Vecchia versione che sfrutta la costruzione preeliminare della sequenza di passi intermedi 

                if (++actCounter < MaxCounter)
                {
                    x = PosSequenceList[actCounter].x;
                    y = PosSequenceList[actCounter].y;

                    if (last_x != x || last_y != y)
                    {
                        if (last_x <= x)
                        {
                            xmin = last_x;
                            xmax = x + TargetSize;
                        }
                        else
                        {
                            xmin = x;
                            xmax = last_x + TargetSize;
                        }

                        if (last_y <= y)
                        {
                            ymin = last_y;
                            ymax = y + TargetSize;
                        }
                        else
                        {
                            ymin = y;
                            ymax = last_y + TargetSize;
                        }

                        last_x = x;
                        last_y = y;
                        myExpW.Invoke(new EventHandler(delegate { myExpW.UpdateTargetCoord(x, y, xmax, xmin, ymax, ymin, bkgrnd); }));

                    }
                }
                else
                {
                    if (bTrainingMode)
                    {
                        FromTrainingToTrials();
                    }
                    else
                    {
                        StopPeriodicTimer();
                        ActualTrialNumber = Trials.Count - 1;
                        EndOfTrials = true;

                        //SaveResults();
                        EndOfTest();
                        //CoordUpdateTimer.Stop();
                    }
                }

            }

            else
            {
                //Nuova versione che invece valuta direttamente le coordinate a partire dalle regole di movimento



            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="new_t"></param>
        /// <param name="act_move"></param>
        /// <returns></returns>
        private bool UpdatePosition(ref int x0, ref int y0, ref int x, ref int y, double new_t, int act_move)
        {
            string x_law, y_law, end_type;
            string ID = "";
            int new_x0, new_y0, end_value;
            double x_Speed = 0, x_Accel = 0, y_Speed = 0, y_Accel = 0;
            double x_Radius = 0, x_k_Radius = 0, x_Omega = 0, x_Omega0 = 0;
            double y_Radius = 0, y_k_Radius = 0, y_Omega = 0, y_Omega0 = 0;
            double t = 0;
            //bool bChangeMove = false;

            bool EndOfMove = false;
            bool StartOfMove = false; 

            XmlNodeList Moves; //TODO: da valutare se trasformare in globale ed assegnarla una sola volta all'inizio del trial

            //se last_t == 0, siamo all'inizio della mossa
            if (last_t == 0)
            {
                StartOfMove = true;
                last_t = new_t;
            }
            else
                StartOfMove = false;

            //calcolo il tempo da passare alle varie leggi
            t = new_t - last_t;

            if (bTrainingMode)
            {
                Moves = TrainingTrials.Item(ActualTrialNumber).SelectNodes("Moves/Move");
                ID = TrainingTrials.Item(ActualTrialNumber).SelectSingleNode("ID").InnerText;
            }
            else
            {
                Moves = Trials.Item(ActualTrialNumber).SelectNodes("Moves/Move");
                ID = Trials.Item(ActualTrialNumber).SelectSingleNode("ID").InnerText;
            }

            x_law = Moves.Item(act_move).SelectSingleNode("X_Law").InnerText;
            y_law = Moves.Item(act_move).SelectSingleNode("Y_Law").InnerText;
            end_type = Moves.Item(act_move).SelectSingleNode("end_type").InnerText;
            new_x0 = int.Parse(Moves.Item(act_move).SelectSingleNode("x0").InnerText);
            new_y0 = int.Parse(Moves.Item(act_move).SelectSingleNode("y0").InnerText);
            end_value = int.Parse(Moves.Item(act_move).SelectSingleNode("end_value").InnerText);


            //imposto il valore iniziale (se è -1, è equivalente al valore della X precedente.
            if (new_x0 < 0)
                x0 = x;
            else
                x0 = new_x0;

            if (new_y0 < 0)
                y0 = y;
            else
                y0 = new_y0;

            switch (x_law)
            {
                case "steady":
                    x_MoveLaw = MoveLaw.Steady;
                    //Se sono all'inizio della mossa, lo segnalo all'ET

                    // *********************************************************
                    //                  DA SISTEMARE
                    //if (StartOfMove && ExpInputMode == InputMode.Eyetracker)
                    //    ET.SendMessage("!V "); 
                    break;
                    // *********************************************************

                case "line":
                    x_MoveLaw = MoveLaw.Line;
                    x_Speed = double.Parse((Moves.Item(act_move).SelectSingleNode("X_speed").InnerText));
                    x_Accel = double.Parse((Moves.Item(act_move).SelectSingleNode("X_accel").InnerText));
                    break;

                case "sin":
                    x_MoveLaw = MoveLaw.Sin;
                    x_Radius = double.Parse((Moves.Item(act_move).SelectSingleNode("X_radius").InnerText));
                    x_k_Radius = double.Parse((Moves.Item(act_move).SelectSingleNode("X_k_radius").InnerText));
                    x_Omega = double.Parse((Moves.Item(act_move).SelectSingleNode("X_omega").InnerText));
                    x_Omega0 = double.Parse((Moves.Item(act_move).SelectSingleNode("X_omega0").InnerText));
                    break;
            }

            switch (y_law)
            {
                case "steady":
                    y_MoveLaw = MoveLaw.Steady;
                    break;

                case "line":
                    y_MoveLaw = MoveLaw.Line;
                    y_Speed = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_speed").InnerText));
                    y_Accel = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_accel").InnerText));
                    break;

                case "sin":
                    y_MoveLaw = MoveLaw.Sin;
                    y_Radius = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_radius").InnerText));
                    y_k_Radius = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_k_radius").InnerText));
                    y_Omega = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_omega").InnerText));
                    y_Omega0 = double.Parse((Moves.Item(act_move).SelectSingleNode("Y_omega0").InnerText));

                    break;
            }


            //Calcolo la X
            switch (x_law)
            {
                case "steady":
                    x = x0;
                    break;

                case "line":
                    x_MoveLaw = MoveLaw.Line;
                    if (x_Accel != 0)
                        x = (int)((double)x0 + x_Speed * t + x_Accel * t * t);
                    else
                        x = (int)((double)x0 + x_Speed * t);
                    break;

                case "sin":
                    x_MoveLaw = MoveLaw.Sin;
                    if (x_k_Radius != 0)
                        x = x0 + (int)(x_Radius * x_k_Radius * t * Math.Sin(x_Omega * t - x_Omega0));
                    else
                        x = x0 + (int)(x_Radius * Math.Sin(x_Omega * t - x_Omega0));
                    break;
            }

            switch (y_law)
            {
                case "steady":
                    y = y0;
                    break;

                case "line":
                    y_MoveLaw = MoveLaw.Line;
                    if (y_Accel != 0)
                        y = (int)((double)y0 + y_Speed * t + y_Accel * t * t);
                    else
                        y = (int)((double)y0 + y_Speed * t);
                    break;

                case "sin":
                    y_MoveLaw = MoveLaw.Sin;
                    if (y_k_Radius != 0)
                        y = y0 + (int)(y_Radius * y_k_Radius * t * Math.Sin(y_Omega * t - y_Omega0));
                    else
                        y = y0 + (int)(y_Radius * Math.Sin(y_Omega * t - y_Omega0));
                    break;

            }

            switch (end_type)
            {
                case "time":
                    if ((int)(t * 1000.0) > end_value)
                        EndOfMove = true;

                    break;
                case "x_pos_max":
                    if (x > end_value)
                        EndOfMove = true;

                    break;
                case "y_pos_max":
                    if (y > end_value)
                        EndOfMove = true;

                    break;
                case "x_pos_min":
                    if (x < end_value)
                        EndOfMove = true;

                    break;
                case "y_pos_min":
                    if (y < end_value)
                        EndOfMove = true;
                    break;
            }

            //se ho terminato la mossa, azzero la variabile per il calcolo del tempo
            if (EndOfMove)
                last_t = 0;


            return EndOfMove;
        }



        /// <summary>
        /// 
        /// </summary>
        private void CreatePosSequence()
        {
            string x_law, y_law, end_type;
            string ID = "";
            int x0 = 0, y0 = 0, new_x0, new_y0, end_value;
            double x_Speed = 0, x_Accel = 0, y_Speed = 0, y_Accel = 0;
            double x_Radius = 0, x_k_Radius = 0, x_Omega = 0, x_Omega0 = 0;
            double y_Radius = 0, y_k_Radius = 0, y_Omega = 0, y_Omega0 = 0;
            int maxcount = 0;

            int delta_t = 0; //incremento in ms

            XmlNodeList Moves;

            if (bTrainingMode)
                maxcount = TrainingTrials.Count;
            else
                maxcount = Trials.Count;

            //ciclo su tutti i Trials
            for (int i = 0; i < maxcount; i++)
            {

                if (bTrainingMode)
                {
                    Moves = TrainingTrials.Item(i).SelectNodes("Moves/Move");
                    ID = TrainingTrials.Item(i).SelectSingleNode("ID").InnerText;
                }
                else
                {
                    Moves = Trials.Item(i).SelectNodes("Moves/Move");
                    ID = Trials.Item(i).SelectSingleNode("ID").InnerText;
                }

                //ciclo su tutti gli spostamenti (move)
                for (int j = 0; j < Moves.Count; j++)
                {
                    bool EndOfMove = false;
                    double t = 0;
                    //leggo l'interno dello spostamento per capire quali sono le regole da seguire

                    x_law = Moves.Item(j).SelectSingleNode("X_Law").InnerText;
                    y_law = Moves.Item(j).SelectSingleNode("Y_Law").InnerText;
                    end_type = Moves.Item(j).SelectSingleNode("end_type").InnerText;
                    new_x0 = int.Parse(Moves.Item(j).SelectSingleNode("x0").InnerText);
                    new_y0 = int.Parse(Moves.Item(j).SelectSingleNode("y0").InnerText);
                    end_value = int.Parse(Moves.Item(j).SelectSingleNode("end_value").InnerText);

                    //imposto il valore iniziale (se è -1, è equivalente al valore della X precedente.
                    if (new_x0 < 0)
                        x0 = x;
                    else
                        x0 = new_x0;

                    if (new_y0 < 0)
                        y0 = y;
                    else
                        y0 = new_y0;

                    switch (x_law)
                    {
                        case "steady":
                            x_MoveLaw = MoveLaw.Steady;
                            break;

                        case "line":
                            x_MoveLaw = MoveLaw.Line;
                            x_Speed = double.Parse((Moves.Item(j).SelectSingleNode("X_speed").InnerText));
                            x_Accel = double.Parse((Moves.Item(j).SelectSingleNode("X_accel").InnerText));
                            break;

                        case "sin":
                            x_MoveLaw = MoveLaw.Sin;
                            x_Radius = double.Parse((Moves.Item(j).SelectSingleNode("X_radius").InnerText));
                            x_k_Radius = double.Parse((Moves.Item(j).SelectSingleNode("X_k_radius").InnerText));
                            x_Omega = double.Parse((Moves.Item(j).SelectSingleNode("X_omega").InnerText));
                            x_Omega0 = double.Parse((Moves.Item(j).SelectSingleNode("X_omega0").InnerText));
                            break;
                    }

                    switch (y_law)
                    {
                        case "steady":
                            y_MoveLaw = MoveLaw.Steady;
                            break;

                        case "line":
                            y_MoveLaw = MoveLaw.Line;
                            y_Speed = double.Parse((Moves.Item(j).SelectSingleNode("Y_speed").InnerText));
                            y_Accel = double.Parse((Moves.Item(j).SelectSingleNode("Y_accel").InnerText));
                            break;

                        case "sin":
                            y_MoveLaw = MoveLaw.Sin;
                            y_Radius = double.Parse((Moves.Item(j).SelectSingleNode("Y_radius").InnerText));
                            y_k_Radius = double.Parse((Moves.Item(j).SelectSingleNode("Y_k_radius").InnerText));
                            y_Omega = double.Parse((Moves.Item(j).SelectSingleNode("Y_omega").InnerText));
                            y_Omega0 = double.Parse((Moves.Item(j).SelectSingleNode("Y_omega0").InnerText));

                            break;
                    }

                    while (!EndOfMove)
                    {
                        //Calcolo la X
                        switch (x_law)
                        {
                            case "steady":
                                x = x0;
                                break;

                            case "line":
                                x_MoveLaw = MoveLaw.Line;
                                if (x_Accel != 0)
                                    x = (int)((double)x0 + x_Speed * t + x_Accel * t * t);
                                else
                                    x = (int)((double)x0 + x_Speed * t);
                                break;

                            case "sin":
                                x_MoveLaw = MoveLaw.Sin;
                                if (x_k_Radius != 0)
                                    x = x0 + (int)(x_Radius * x_k_Radius * t * Math.Sin(x_Omega * t - x_Omega0));
                                else
                                    x = x0 + (int)(x_Radius * Math.Sin(x_Omega * t - x_Omega0));
                                break;
                        }

                        switch (y_law)
                        {
                            case "steady":
                                y = y0;
                                break;

                            case "line":
                                y_MoveLaw = MoveLaw.Line;
                                if (y_Accel != 0)
                                    y = (int)((double)y0 + y_Speed * t + y_Accel * t * t);
                                else
                                    y = (int)((double)y0 + y_Speed * t);
                                break;

                            case "sin":
                                y_MoveLaw = MoveLaw.Sin;
                                if (y_k_Radius != 0)
                                    y = y0 + (int)(y_Radius * y_k_Radius * t * Math.Sin(y_Omega * t - y_Omega0));
                                else
                                    y = y0 + (int)(y_Radius * Math.Sin(y_Omega * t - y_Omega0));
                                break;

                        }

                        switch (end_type)
                        {
                            case "time":
                                if ((int)(t * 1000.0) > end_value)
                                    EndOfMove = true;

                                break;
                            case "x_pos_max":
                                if (x > end_value)
                                    EndOfMove = true;

                                break;
                            case "y_pos_max":
                                if (y > end_value)
                                    EndOfMove = true;

                                break;
                            case "x_pos_min":
                                if (x < end_value)
                                    EndOfMove = true;

                                break;
                            case "y_pos_min":
                                if (y < end_value)
                                    EndOfMove = true;
                                break;
                        }


                        if (!EndOfMove)
                        {
                            //aggiorno la X
                            PosSequenceList.Add(new PosSequenceElem(ID, "", x, y, delta_t));
                            //Console.WriteLine("ID=" + ID.ToString() + " x=" + x.ToString() + " y=" + y.ToString());
                        }

                        delta_t += UpdateTime;
                        t = t + (double)UpdateTime / 1000.0;

                    }
                    MaxCounter = PosSequenceList.Count;
                }
            }
        }

        public override void StartPeriodicTimer()
        {
            x = PosSequenceList[0].x;
            y = PosSequenceList[0].y;

            StartMotionTime = DateTime.Now;

            CoordUpdateTimer.Elapsed += new System.Timers.ElapsedEventHandler(UpdateCoordTimerElapsedEvent);
            CoordUpdateTimer.Interval = UpdateTime;

            CoordUpdateTimer.Start();

            //StartTime = DateTime.Now;
            myLog.Log("SmoothPursuit Timer Started");
        }

        public override void StopPeriodicTimer()
        {
            CoordUpdateTimer.Stop();
            CoordUpdateTimer.Elapsed -= UpdateCoordTimerElapsedEvent;
            myLog.Log("SmoothPursuit Timer Stoppped");
        }


    }
}
